package com.lemon.springcloud.core.listener.def;

import com.lemon.core.common.lang.util.StringUtils;
import com.lemon.core.conf.ZKClient;
import com.lemon.springcloud.core.bean.ServiceInfo;
import com.lemon.springcloud.core.config.LemonDefaultApplicationConf;
import com.lemon.springcloud.core.config.LemonSpringBootConfig;
import com.lemon.springcloud.core.initial.LemonApplicationInitializer;
import com.lemon.springcloud.core.listener.LemonBootStarterListener;
import com.lemon.springcloud.core.listener.ShutdownListener;
import com.lemon.springcloud.core.util.PropertiesUtils;
import com.lemon.springcloud.core.util.SpringContextUtils;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.apache.zookeeper.KeeperException;
import org.springframework.beans.BeanUtils;
import org.springframework.boot.Banner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringApplicationRunListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.env.*;
import org.springframework.core.io.support.SpringFactoriesLoader;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import java.io.IOException;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.util.*;

/**
 * @author hdzhang
 * @ClassName LemonBasicSpringApplicationRunListener
 * @desc
 * @Date 2018/9/7 21:43
 * @Version 1.0
 **/
public class LemonBasicSpringApplicationRunListener implements SpringApplicationRunListener {

    private final static Logger LOGGER = LogManager.getLogger(LemonBasicSpringApplicationRunListener.class);

    private static ServiceInfo serviceInfo;

    private static LemonStarterListeners starterListeners;

    private static boolean initialized = false;

    private final SpringApplication application;
    private final String[] args;

    public LemonBasicSpringApplicationRunListener(SpringApplication application, String[] args) {
        this.application = application;
        application.setBannerMode(Banner.Mode.OFF);
        this.args = args;
        this.serviceInfo = new ServiceInfo();
    }

    @Override
    public void starting() {

    }

    /**
     * 初始化启动监听器
     */
    private void initStarterListeners() {
        if (null == starterListeners) {
            synchronized (LemonBasicSpringApplicationRunListener.class) {
                ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
                List<String> names = SpringFactoriesLoader.loadFactoryNames(LemonBootStarterListener.class, classLoader);

                List<LemonBootStarterListener> instances = new ArrayList<LemonBootStarterListener>(names.size());
                for (String name : names) {
                    try {
                        Class<?> instanceClass = ClassUtils.forName(name, classLoader);
                        Assert.isAssignable(LemonBootStarterListener.class, instanceClass);
                        Constructor<?> constructor = instanceClass.getDeclaredConstructor();
                        LemonBootStarterListener instance = (LemonBootStarterListener) BeanUtils.instantiateClass(constructor);
                        instances.add(instance);
                    } catch (Throwable ex) {
                        throw new IllegalArgumentException("Cannot instantiate " + LemonBootStarterListener.class + " : " + name, ex);
                    }
                }
                starterListeners = new LemonStarterListeners(instances);
                starterListeners.onStarting();
            }
        }
    }

    @Override
    public void environmentPrepared(ConfigurableEnvironment environment) {
        serviceInfo.setJavaRuntime(environment.getProperty("java.runtime.version") + "\t"
                + environment.getProperty("java.class.version"));
        LemonDefaultApplicationConf.init(LemonBasicSpringApplicationRunListener.class.getClassLoader());
        this.coverApplicationProperties(environment);
    }

    /**
     * 根据运行环境，在此方法内可以覆盖配置文件的配置
     * @param environment
     */
    private void coverApplicationProperties(ConfigurableEnvironment environment) {
        List<PropertySource> applicationConfigurationPropertiesSource = new LinkedList<>();
        PropertySource<?> applicationConfigurationProperties = ((ConfigurableEnvironment) environment).getPropertySources()
                .get("applicationConfigurationProperties");
        if (applicationConfigurationProperties == null) {
            applicationConfigurationProperties = ((ConfigurableEnvironment) environment).getPropertySources()
                    .get("applicationConfig: [classpath:/application.properties]");
            applicationConfigurationPropertiesSource.add(applicationConfigurationProperties);
        } else {
            applicationConfigurationPropertiesSource =
                    (List<PropertySource>) applicationConfigurationProperties.getSource();
        }

        if (applicationConfigurationProperties == null) {
            return;
        }
        if (applicationConfigurationPropertiesSource == null) {
            return;
        }
        for (PropertySource propertySource : applicationConfigurationPropertiesSource) {
            if (propertySource.getSource() instanceof Set) {
                Set sourceSet = (Set) propertySource.getSource();
                if (sourceSet == null) {
                    continue;
                }
                for (Object o : sourceSet) {
                    if (o instanceof MapPropertySource) {
                        Map<String, Object> properties = ((MapPropertySource) o).getSource();
                        // register zookeeper property configurations
                        this.registerZkConfigPropertySource(environment, properties);
                    }
                }
            } else if (propertySource.getSource() instanceof LinkedHashMap) {
                LinkedHashMap hashMap = (LinkedHashMap) propertySource.getSource();
                if (hashMap == null) {
                    continue;
                }
                this.registerZkConfigPropertySource(environment, hashMap);
            }

        }
    }

    private void registerZkConfigPropertySource(ConfigurableEnvironment env, Map<String, Object> properties) {
        Object zkConfigPaths = properties.get("lemon.zookeeper.conf.properties");
        if (zkConfigPaths != null) {
            PropertySource<?> zkConfigPropertySource = env.getPropertySources().get("zkConfigPropertySource");
            if (zkConfigPropertySource == null) {
                zkConfigPropertySource = new PropertiesPropertySource("zkConfigPropertySource", new Properties());
                env.getPropertySources().addLast(zkConfigPropertySource);
            }
            String[] zkConfigPathArray = String.valueOf(zkConfigPaths).split(";");
            Object zkConfigHost = properties.get("lemon.zookeeper.default.host");
            Object zkConfigPort = properties.get("lemon.zookeeper.default.port");
            if (zkConfigHost == null || zkConfigPort == null) {
                String prefixKey = "lemon.zookeeper.default." + ServiceInfo.ENV_NAME;
                Set<String> keySet = PropertiesUtils.getPrefixSigns(prefixKey);
                Iterator keyItor = keySet.iterator();
                while (keyItor.hasNext()) {
                    String key = keyItor.next().toString();
                    String hostKey = prefixKey + key + ".host";
                    String portKey = prefixKey + key + ".port";
                    zkConfigHost = PropertiesUtils.getString(hostKey);
                    zkConfigPort = PropertiesUtils.getString(portKey);
                }
            }

            if (zkConfigHost != null && StringUtils.isNotBlank(zkConfigHost.toString())
                    && zkConfigPort != null && StringUtils.isNotBlank(zkConfigPort.toString())
                    && LemonSpringBootConfig.getZookeeperGroupInfo().size() == 0) {
                LemonSpringBootConfig.addZookeeperGroupInfo("zookeeper.default", zkConfigHost.toString() + ":" + zkConfigPort.toString());
            }

            ZKClient client = ZKClient.get(LemonSpringBootConfig.getZookeeperGroupInfo().get("zookeeper.default"));
            for (String path : zkConfigPathArray) {
                //logger.info("MOP-LOGGER - Loading zookeeper properties file: [" + client.getConnAddress() + "][" + path + "].");
                try {
                    if (!client.existed(path)) {
                        //logger.warn("MOP-LOGGER - Could not found zk node: [" + client.getConnAddress() + "][" + path + "].");
                        continue;
                    }
                    String pathData = client.getStringData(path);

                    // watch string value change and notify configuration properties
                    client.watchStrValueNode(path, changeData -> LemonApplicationInitializer.notifyZkPropertyChange(path, changeData));

                    Properties prop = (Properties) zkConfigPropertySource.getSource();
                    Properties tmpProp = new Properties();
                    tmpProp.load(new StringReader(pathData));
                    for (Object key : tmpProp.keySet()) {
                        String strKey = String.valueOf(key);
                        LemonApplicationInitializer.addZkPropertyKeyMapping(strKey, path);
                    }

                    // set environment properties to provide inject values
                    prop.load(new StringReader(pathData));

                    PropertySource<?> applicationConfigPropertySource = env.getPropertySources().get("applicationConfigurationProperties");
                    if (applicationConfigPropertySource == null) {
                        applicationConfigPropertySource = new PropertiesPropertySource("applicationConfigurationProperties", prop);
                        env.getPropertySources().addLast(applicationConfigPropertySource);
                    }

                    PropertySource<?> defaultConfigPropertySource = env.getPropertySources().get("applicationConfig: [classpath:/application.properties]");
                    if (defaultConfigPropertySource == null) {
                        applicationConfigPropertySource = new PropertiesPropertySource("applicationConfig: [classpath:/application.properties]", prop);
                        env.getPropertySources().addLast(defaultConfigPropertySource);
                    } else {
                        LinkedHashMap hashMap = (LinkedHashMap) defaultConfigPropertySource.getSource();
                        for (Object key : prop.keySet()) {
                            hashMap.put(key, prop.get(key));
                        }
                    }
                } catch (KeeperException | InterruptedException | IOException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 自定义监听器初始化准备
     * @param context
     */
    @Override
    public void contextPrepared(ConfigurableApplicationContext context) {
        if (null != starterListeners) {
            starterListeners.onPrepared(context);
        }
    }

    @Override
    public void contextLoaded(ConfigurableApplicationContext context) {
        if (null != starterListeners) {
            starterListeners.onPrepared(context);
        }
    }

    @Override
    public void started(ConfigurableApplicationContext context) {
        this.serviceInfo.setStartTime(System.currentTimeMillis());
        this.initStarterListeners();
        ServiceInfo.setServiceUUID(UUID.randomUUID().toString());


    }

    @Override
    public void running(ConfigurableApplicationContext context) {
        this.initServerInfo(context);

        // context懒加载
        SpringContextUtils.initInstance(context);

        Boolean initializerEnable = LemonApplicationInitializer.isLemonApplicationInitializerEnable();
        if (initializerEnable) {

        }

        if (null != starterListeners) {
            starterListeners.onFinished(context);
        }

        serviceInfo.setStarterDuration(System.currentTimeMillis() - serviceInfo.getStartTime());

        if (initializerEnable) {
            LOGGER.info("LEMON-LOGGER - On LEMON spring boot api started.");

            LemonApplicationInitializer initializer;
            initializer = context.getBean(LemonApplicationInitializer.class);
            //LemonSpringBootConfig lemonSpringBootConfig = context.getBean(LemonSpringBootConfig.class);
            //initializer.setLemonSpringBootConfig(lemonSpringBootConfig);

            initializer.setLemonStarterListeners(starterListeners);

            // Shutdown listener
            ShutdownListener listener = this.getShutdownListener(initializer);

            LOGGER.info("LEMON-LOGGER - Using @EnableLemonAutoConfiguration:\n" + initializer.printAppStartInfo(serviceInfo));

            this.registerShutdownHook(context, initializer, listener);
        }
    }

    @Override
    public void failed(ConfigurableApplicationContext context, Throwable exception) {

    }

    /**
     * 初始化当前服务信息
     * @param context ApplicationContext
     */
    private void initServerInfo(ConfigurableApplicationContext context) {
        Environment environment = context.getEnvironment();
        String ip = StringUtils.isNoneBlank(environment.getProperty("server.address")) ?
                environment.getProperty("server.address") : "127.0.0.1";
        String port = StringUtils.isNoneBlank(environment.getProperty("server.port")) ?
                environment.getProperty("server.port") : "undefined";
        String applicationName = StringUtils.isNoneBlank(environment.getProperty("server.context-path")) ?
                environment.getProperty("server.context-path") : "/";

        serviceInfo.setIp(ip);
        serviceInfo.setServerPort(port);
        ServiceInfo.setApplicationName(applicationName);
    }

    /**
     * 注册停机回调
     *
     * @param context          ApplicationContext
     * @param initializer      应用基础初始化器
     * @param listener         停机监听器
     */
    private void registerShutdownHook(ConfigurableApplicationContext context,
                                      LemonApplicationInitializer initializer,
                                      ShutdownListener listener) {
        if (!initialized) {
            synchronized (LemonBasicSpringApplicationRunListener.class) {
                if (!initialized) {
                    initialized = true;

                    Runtime.getRuntime().addShutdownHook(new Thread() {
                        @Override
                        public void run() {
                            if (listener != null)
                                listener.beforeShutdown(context);

                            // 停机处理中的通知关闭 2018-06-27 -- By Chim
                            // this.notifyPostProcessing();

                            if (listener != null)
                                listener.onShutdown(context);
                            // 处理完成所有未处理的数据

                            if (listener != null)
                                listener.stopped(context);

                            serviceInfo.setEndTime(System.currentTimeMillis());

                            LOGGER.info("LEMON-LOGGER - Using @EnableLemonAutoConfiguration:\n" + initializer.printAppEndInfo(serviceInfo));
                        }
                    });
                }
            }
        }
    }

    /**
     * 获得停机监听器
     *
     * @param initializer 猫扑基础初始化器
     * @return 停机监听器
     */
    private ShutdownListener getShutdownListener(LemonApplicationInitializer initializer) {
        ShutdownListener listener = initializer.getShutdownListener();
        if (listener != null) {
            serviceInfo.setShutdownListenerRegistered(true);
            LOGGER.info("LEMON-LOGGER - Found lemon application initializer, register shutdown listener.");
        } else {
            LOGGER.warn("LEMON-LOGGER - Could not found any shutdown listener in current application.");
        }
        return listener;
    }
}
